package com.sk.util;

import lombok.SneakyThrows;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Random;

/**
 * 安全与加密工具类。
 * <p/>MessageDigest:常用标准内容摘要算法，md5,sha
 * <p/>Cipher:对称加密算法和非对称加密算法，支持解密
 * <p/>--加密算法有4种：AES、DES、DESede、RSA
 * <p/>--反馈模式有2种：CBC(有向量模式)、ECB(无向量模式)
 * <p/>--填充方案有5种：NoPadding 、PKCS5Padding、PKCS1Padding 、OAEPWithSHA-1AndMGF1Padding、OAEPWithSHA-256AndMGF1Padding
 * <p/>Signature:基于加密算法的签名和验签的算法
 *
 * @author smy
 * {@code @date} 2023/3/14
 */
public class SecurityUtil {
    private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    public static String toHex(byte[] bytes) {
        char[] chars = new char[bytes.length * 2];
        for (int i = 0; i < chars.length; i = i + 2) {
            byte b = bytes[i / 2];
            chars[i] = HEX_CHARS[(b >>> 0x4) & 0xf];
            chars[i + 1] = HEX_CHARS[b & 0xf];
        }
        return new String(chars);
    }

    public static byte[] parseHex(String value) {
        byte[] bytes = new byte[value.length() / 2];
        for (int i = 0; i < value.length(); i += 2) {
            // 两位一组，表示一个字节,把这样表示的16进制字符串，还原成一个字节
            bytes[i / 2] = (byte) ((Character.digit(value.charAt(i), 16) << 4) + Character.digit(value.charAt(i + 1), 16));
        }
        return bytes;
    }
    private static final Random random = new SecureRandom();

    public static String algorithm(String algorithm) {
        return algorithm.split("/")[0];
    }

    //对称算法
    private static final String[] symmetric = new String[]{"AES", "DES", "Hmac", "RC"};
    //非对称算法
    private static final String[] asymmetric = new String[]{"RSA", "DSA", "DiffieHellman", "EC"};

    public static boolean isSymmetric(String algorithm) {
        if (StringUtil.startsWith(algorithm, symmetric)) {
            return true;
        }
        if (StringUtil.startsWith(algorithm, asymmetric)) {
            return false;
        }
        throw new RuntimeException("check symmetric error of " + algorithm);
    }

    public static SecretKeySpec loadSecretKey(String algorithm, String value) {
        return loadSecretKey(algorithm, Base64Utils.decodeFromString(value));
    }

    public static SecretKeySpec loadSecretKey(String algorithm, byte[] bytes) {
        return new SecretKeySpec(bytes, algorithm);
    }

    public static PrivateKey loadPrivateKey(String algorithm, String value) {
        return loadPrivateKey(algorithm, Base64Utils.decodeFromString(value));
    }

    @SneakyThrows
    public static PrivateKey loadPrivateKey(String algorithm, byte[] bytes) {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        return keyFactory.generatePrivate(keySpec);
    }

    public static PublicKey loadPublicKey(String algorithm, String value) {
        return loadPublicKey(algorithm, Base64Utils.decodeFromString(value));
    }

    @SneakyThrows
    public static PublicKey loadPublicKey(String algorithm, byte[] bytes) {
        X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(bytes);
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        return keyFactory.generatePublic(pubX509);
    }

    public static Key loadEncodeKey(String algorithm, String value) {
        return loadEncodeKey(algorithm, Base64Utils.decodeFromString(value));
    }

    public static Key loadEncodeKey(String algorithm, byte[] bytes) {
        return isSymmetric(algorithm) ? loadSecretKey(algorithm, bytes) : loadPublicKey(algorithm, bytes);
    }

    public static Key loadDecodeKey(String algorithm, String value) {
        return loadDecodeKey(algorithm, Base64Utils.decodeFromString(value));
    }

    public static Key loadDecodeKey(String algorithm, byte[] bytes) {
        return isSymmetric(algorithm) ? loadSecretKey(algorithm, bytes) : loadPrivateKey(algorithm, bytes);
    }


    public static String key2base64(Key key) {
        return Base64Utils.encodeToString(key.getEncoded());
    }

    public static byte[] genRandom(int length) {
        byte[] bytes = new byte[length];
        random.nextBytes(bytes);
        return bytes;
    }

    public static String genRandom2Base64(int length) {
        return Base64Utils.encodeToString(genRandom(length));
    }

    /**
     * genSecretKey("AES",128/192/256)
     *
     * @param algorithm 加密算法
     * @param length    秘钥长度>0
     * @return 秘钥生成器
     */
    @SneakyThrows
    public static SecretKey genSecretKey(String algorithm, int length) {
        KeyGenerator gen = KeyGenerator.getInstance(algorithm);
        if (length > 0) {
            gen.init(length);
        }
        return gen.generateKey();
    }

    /**
     * getKeyPair("RSA",1204/2048)
     *
     * @param algorithm 加密算法
     * @param length    秘钥长度
     * @return 秘钥生成器
     */
    @SneakyThrows
    public static KeyPair getKeyPair(String algorithm, int length) {
        KeyPairGenerator gen = KeyPairGenerator.getInstance(algorithm);
        gen.initialize(length);
        return gen.generateKeyPair();
    }

    public static String encodeByCipher(String algorithm, String value, Key key) {
        return Base64Utils.encodeToString(encodeByCipher(algorithm, value.getBytes(), key));
    }

    public static String encodeByCipher(String algorithm, String value, String iv, Key key) {
        return Base64Utils.encodeToString((encodeByCipher(algorithm, value.getBytes(), Base64Utils.decodeFromString(iv), key)));
    }


    @SneakyThrows
    public static byte[] encodeByCipher(String algorithm, byte[] bytes, Key key) {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(bytes);
    }

    @SneakyThrows
    public static byte[] encodeByCipher(String algorithm, byte[] bytes, byte[] ivBytes, Key key) {
        Cipher cipher = Cipher.getInstance(algorithm);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
        return cipher.doFinal(bytes);
    }

    public static String decodeByCipher(String algorithm, String value, Key key) {
        return new String(decodeByCipher(algorithm, Base64Utils.decodeFromString(value), key));
    }

    public static String decodeByCipher(String algorithm, String value, String iv, Key key) {
        return new String(decodeByCipher(algorithm, Base64Utils.decodeFromString(value), Base64Utils.decodeFromString(iv), key));
    }

    @SneakyThrows
    public static byte[] decodeByCipher(String algorithm, byte[] bytes, Key key) {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(bytes);
    }

    @SneakyThrows
    public static byte[] decodeByCipher(String algorithm, byte[] bytes, byte[] ivBytes, Key key) {
        Cipher cipher = Cipher.getInstance(algorithm);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
        cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
        return cipher.doFinal(bytes);
    }

    @SneakyThrows
    public static byte[] sign(String algorithm, byte[] bytes, PrivateKey privateKey) {
        Signature signature = Signature.getInstance(algorithm);
        signature.initSign(privateKey);
        signature.update(bytes);
        return signature.sign();
    }

    @SneakyThrows
    public static boolean verify(String algorithm, byte[] bytes, byte[] sign, PublicKey publicKey) {
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(publicKey);
        signature.update(bytes);
        return signature.verify(sign);
    }

}
